## Civcraft: SC Mod Combat Python Utilities

import CvUtil
from CvPythonExtensions import *

# globals
gc = CyGlobalContext()

# Lambda function for checking if pointers are valid.
isNotValid = lambda x: not x or x.isNone()

# Each of the following attack functions return boolean True if any combat
# did occur, boolean False otherwise.

#
# Single attack
#
def singleAttack(pAttackUnit, pDefendPlot):
    if isNotValid(pAttackUnit):
        print "Not valid attacker"
        return False

    if isNotValid(pDefendPlot):
        print "Not valid defender"
        return False

    pTargetUnit = pDefendPlot.getBestDefenderSC(pAttackUnit, False)
    if isNotValid(pTargetUnit):
        print "No valid target found"
        return False

    combat(pAttackUnit, pTargetUnit)
    return True

#
# Single attack Targetted
#
def singleAttackTargetted(pAttackUnit, pDefendUnit):
    if isNotValid(pAttackUnit):
        print "Not valid attacker"
        return False

    if isNotValid(pDefendUnit):
        print "Not valid defender"
        return False

    if not pAttackUnit.canSCAttackUnit(pDefendUnit, False):
        print "Cannot attack"
        return False

    combat(pAttackUnit, pDefendUnit)
    return True

#
# Turn Attack
#
def turnAttack(pAttackUnit, pDefendPlot):
    if isNotValid(pAttackUnit):
        print "Not valid attacker"
        return False

    if isNotValid(pDefendPlot):
        print "Not valid defender"
        return False

    bCombat = False
    while pAttackUnit.hasMoreAttacksLeft():
        pTargetUnit = pDefendPlot.getBestDefenderSC(pAttackUnit, False)
        if isNotValid(pTargetUnit):
            print "No valid target found"
            break

        combat(pAttackUnit, pTargetUnit)
        bCombat = True

    return bCombat

#
# Turn attack Targetted
#
def turnAttackTargetted(pAttackUnit, pDefendUnit):
    if isNotValid(pAttackUnit):
        print "Not valid attacker"
        return False

    if isNotValid(pDefendUnit):
        print "Not valid defender"
        return False

    if not pAttackUnit.canSCAttackUnit(pDefendUnit, False):
        print "Cannot attack"
        return False

    bCombat = False
    while pAttackUnit.hasMoreAttacksLeft() and not pDefendUnit.isDead():
        combat(pAttackUnit, pDefendUnit)
        bCombat = True

    return bCombat

#
# Single Lurker attack
#
def singleLurkerAttack(pAttackUnit, pDefendPlot):
    if isNotValid(pAttackUnit):
        print "Not valid attacker"
        return False

    if isNotValid(pDefendPlot):
        print "Not valid defender"
        return False

    for iUnit in range(pDefendPlot.getNumUnits()):
        pTargetUnit = pDefendPlot.getUnit(iUnit)
        if pAttackUnit.canSCAttackUnit(pTargetUnit, True):
            combat(pAttackUnit, pTargetUnit, False)

    return True

#
# Goes through everything combat does, from costing the attackers attack points,
# to dealing damage and killing possible cities whose buildings died
#
def combat(pAttackUnit, pDefendUnit, bDecrementMoves = True):
    if bDecrementMoves:
        pAttackUnit.changeAttacks(1) # Cost them one attack
        pAttackUnit.changeMoves(gc.getMOVE_DENOMINATOR()) # Cost them one move
        pAttackUnit.forceCooldown()

    iDamage = getUnitDamage(pAttackUnit, pDefendUnit)

    # Check for damage versus this unit
    pDefendUnit.doDamage(int(round(iDamage)), pAttackUnit.getOwner())

    lKilledUnits = []
    lKilledUnits = doSplashDamage(pAttackUnit, pDefendUnit)
    CvUtil.pyPrint("Units killed: %d" % len(lKilledUnits))

    if (pDefendUnit.isDead()):
        lKilledUnits.append(pDefendUnit)

    # Modified by Gerikes to destroy the city if a unit dies due to city
    for pUnit in lKilledUnits:
        if (gc.getUnitInfo(pUnit.getUnitType()).isBuilding()):
            # GERIKESTODO Handle cities with addons.
            CvUtil.pyPrint("Destroying city at (%d, %d)...")
            pUnit.plot().getPlotCity().kill()
    

    
    CvUtil.pyPrint("Player %ds %s attacked player %ds %s for %d damage." %
                   (pAttackUnit.getOwner(), pAttackUnit.getName(),
                    pDefendUnit.getOwner(), pDefendUnit.getName(),
                    iDamage)
                   )
    return

#
# Determines the attack range needed for a unit to attack
# another unit. Basically, returns either the groundRange
# or airRange, dependent upon if the defending unit is a 
# ground or air unit
#
def determineAttackRangeNeeded(pAttackUnit, pDefendUnit):
    if (pDefendUnit.getDomainType() == DomainTypes.DOMAIN_LAND):
        return pDefendUnit.getGroundRange()
    elif (pDefendUnit.getDomainType() == DomainTypes.DOMAIN_FLOAT):
        return pDefendUnit.getAirRange()
    else:
        CvUtil.pyAssert(False, "In determineAttackRangeNeeded, unit has unknown domain type.")

#
# Does splash damage to units that need splashing done to them.
# For each unit that is killed by Splash damage, the unit's instance
# is added to a list. This list is returned.
#
def doSplashDamage(pAttackUnit, pDefendUnit):
    pAttackInfo = gc.getUnitInfo(pAttackUnit.getUnitType())
    pDefendInfo = gc.getUnitInfo(pDefendUnit.getUnitType())
    pDefenderPlot = pDefendUnit.plot()

    lKilledUnits = []

    if (pDefendUnit.getDomainType() == DomainTypes.DOMAIN_LAND):
        if (pAttackInfo.isGroundSmallSplashAttack()):
            pass
        if (pAttackInfo.isGroundSplashAttack()):
            for iI in range(pDefenderPlot.getNumUnits()):
                pLoopUnit = pDefenderPlot.getUnit(iI)
                if (pLoopUnit.getID() != pDefendUnit.getID() and pLoopUnit.getDomainType() == DomainTypes.DOMAIN_LAND):
                    pLoopUnit.doDamage(int(round(getUnitDamage(pAttackUnit, pLoopUnit) * gc.getDefineFLOAT("SPLASH_DAMAGE_MULTIPLIER"))), pAttackUnit.getOwner() )
                    if (pLoopUnit.isDead()):
                        lKilledUnits.append(pLoopUnit)

        if (pAttackInfo.isGroundLargeSplashAttack()):
            # Go through each adjacent tile:
            for iX in range(-1, 2,1):
                for iY in range(-1,2,1):
                    pPlot = CyMap().plot(pDefenderPlot.getX() + iX, pDefenderPlot.getY() + iY)

                    # In each tile, go through each unit affected by the splash (not counting that actual unit attacked)
                    for iZ in range(pPlot.getNumUnits()):
                        pLoopUnit = pPlot.getUnit(iZ)
                        if (pLoopUnit.getID() != pDefendUnit.getID() and
                            pLoopUnit.getDomainType() == DomainTypes.DOMAIN_LAND):

                            # If this is the spot of the actual attack...
                            if (iX == iY == 0):
                                pLoopUnit.doDamage(int(round(getUnitDamage(pAttackUnit, pLoopUnit) * gc.getDefineFLOAT("LARGE_SPLASH_DAMAGE_SAME_TILE_MULTIPLIER"))),
                                                   pAttackUnit.getOwner())
                            # If this was an adjacent plot...
                            else:
                                pLoopUnit.doDamage(int(round(getUnitDamage(pAttackUnit, pLoopUnit) * gc.getDefineFLOAT("LARGE_SPLASH_DAMAGE_ADJACENT_TILE_MULTIPLIER"))),
                                                   pAttackUnit.getOwner())

                            if (pLoopUnit.isDead()):
                                lKilledUnits.append(pLoopUnit)


                                                   
                            


    elif (pDefendUnit.getDomainType() == DomainTypes.DOMAIN_FLOAT):
       pass

    return lKilledUnits


#
# Gets the damage one unit does to another, checking for all modifiers
# 
def getUnitDamage(pAttackUnit, pDefendUnit):
    iDamage = 0.0
    iDamageDoneToShields = 0.0
    pAttackInfo = gc.getUnitInfo(pAttackUnit.getUnitType())
    pDefendInfo = gc.getUnitInfo(pDefendUnit.getUnitType())

    if (pDefendUnit.getDomainType() == DomainTypes.DOMAIN_LAND):
        iDamage = pAttackUnit.getGroundDamage() - pDefendUnit.getShieldArmor()

        # Damage done to protoss shields are always full, 
        if (pDefendUnit.currShields() > iDamage):
            return iDamage

        # Determine how much damage is automatically going to be done to the shields, and subtract that from the total.
        if (pDefendUnit.currShields() > 0):
            iDamageDoneToShields += pDefendUnit.currShields()
            iDamage -= iDamageDoneToShields

        iDamage -= pDefendUnit.getArmor()

        # iDamage should now hold how much damage to do to the rest of the unit, after subtracting shields and armor

        # For any damage done to the unit's HP, determine modifiers
        if (pAttackInfo.isGroundExplosiveAttack()):
            if (pDefendInfo.isSmallSize()):
                iDamage = iDamage * gc.getDefineFLOAT("EXPLOSIVE_DAMAGE_VS_SMALL_UNIT_MULTIPLIER")
            elif (pDefendInfo.isMediumSize()):
                iDamage = iDamage * gc.getDefineFLOAT("EXPLOSIVE_DAMAGE_VS_MEDIUM_UNIT_MULTIPLIER")
        if (pAttackInfo.isGroundConcussiveOrPlasmaAttack()):
            if (pDefendInfo.isMediumSize()):
                iDamage = iDamage * gc.getDefineFLOAT("CONCUSSIVE_OR_PLASMA_DAMAGE_VS_MEDIUM_UNIT_MULTIPLIER")
            elif (pDefendInfo.isLargeSize()):
                iDamage = iDamage * gc.getDefineFLOAT("CONCUSSIVE_OR_PLASMA_DAMAGE_VS_LARGE_UNIT_MULTIPLIER")
            
    elif (pDefendUnit.getDomainType() == DomainTypes.DOMAIN_FLOAT):
        iDamage = pAttackUnit.getAirDamage() - pDefendUnit.getShieldArmor()

        # Damage done to protoss shields are always full, 
        if (pDefendUnit.currShields() > iDamage):
            return iDamage

        # Determine how much damage is automatically going to be done to the shields, and subtract that from the total.
        if (pDefendUnit.currShields() > 0):
            iDamageDoneToShields += pDefendUnit.currShields()
            iDamage -= iDamageDoneToShields

        iDamage -= pDefendUnit.getArmor()

        # iDamage should now hold how much damage to do to the rest of the unit, after subtracting shields and armor

        if (pAttackInfo.isAirExplosiveAttack()):
            if (pDefendInfo.isSmallSize()):
                iDamage = iDamage * gc.getDefineFLOAT("EXPLOSIVE_DAMAGE_VS_SMALL_UNIT_MULTIPLIER")
            elif (pDefendInfo.isMediumSize()):
                iDamage = iDamage * gc.getDefineFLOAT("EXPLOSIVE_DAMAGE_VS_MEDIUM_UNIT_MULTIPLIER")
        if (pAttackInfo.isAirConcussiveOrPlasmaAttack()):
            if (pDefendInfo.isMediumSize()):
                iDamage = iDamage * gc.getDefineFLOAT("CONCUSSIVE_OR_PLASMA_DAMAGE_VS_MEDIUM_UNIT_MULTIPLIER")
            elif (pDefendInfo.isLargeSize()):
                iDamage = iDamage * gc.getDefineFLOAT("CONCUSSIVE_OR_PLASMA_DAMAGE_VS_LARGE_UNIT_MULTIPLIER")


    # Return the modified damage to HP plus the damage that was done to the shield as one value.
    # The doDamage function in the SDK will correctly subtract the damage from each.
    # Note that every attack will always do at least one point of damage.

    return max(1, iDamage + iDamageDoneToShields)
    
